﻿using System;
using System.Threading;
using System.Windows;

namespace WindowsPhoneEssentials.Threading
{
    public static class Execute
    {
        private static Action<Action> onUIThread;
        private static Action<Action> onUIThreadSync;
        private static Func<Func<object>, object> onUIThreadReturning;

        static Execute()
        {
            ResetToDefaults();
        }

        /// <summary>
        /// Dispatches action back to main thread. 
        /// </summary>
        public static void OnUIThread(Action action)
        {
            onUIThread(action);
        }

        /// <summary>
        /// Executes a task on the UI Thread. Be careful as this call is blocking
        /// </summary>
        public static void OnUIThreadSync(Action action)
        {
            onUIThreadSync(action);            
        }

        /// <summary>
        /// Executes a task on the UI Thread. Be careful as this call is blocking
        /// </summary>
        public static T OnUIThreadSync<T>(Func<T> func)
        {
            return (T)onUIThreadReturning(()=>func());
        }

        /// <summary>
        /// If set, calls to OnUIThread* will simply be invoked synchronously, and will not run on UI thread
        /// </summary>
        public static void SetOnUIThreadPassthrough()
        {
            onUIThread = action => ThreadPool.QueueUserWorkItem(s=> action());
            onUIThreadSync = action => action();
            onUIThreadReturning = func => func();
        }

        /// <summary>
        /// Resets OnUIThread methods to default implementations
        /// </summary>
        public static void ResetToDefaults()
        {
            ResetDispatch();
        }

        private static void ResetDispatch()
        {
            onUIThread =
                action =>
                    {
                        if (Deployment.Current.Dispatcher.CheckAccess())
                            action();
                        else
                            Deployment.Current.Dispatcher.BeginInvoke(action);
                    };

            onUIThreadSync =
                action =>
                    {
                        if (Deployment.Current.Dispatcher.CheckAccess())
                            action();
                        else
                        {
                            using (var resetEvent = new ManualResetEvent(false))
                            {
                                Deployment.Current.Dispatcher.BeginInvoke(
                                    () =>
                                        {
                                            action();
                                            resetEvent.Set();
                                        });
                                resetEvent.WaitOne();
                            }
                        }
                    };

            onUIThreadReturning =
                func =>
                    {
                        if (Deployment.Current.Dispatcher.CheckAccess())
                            return func();

                        using (var resetEvent = new ManualResetEvent(false))
                        {
                            object result = null;
                            Deployment.Current.Dispatcher.BeginInvoke(
                                () =>
                                    {
                                        result = func();
                                        resetEvent.Set();
                                    });
                            resetEvent.WaitOne();
                            return result;
                        }
                    };
        }

        public static void AsyncPattern(
            Func<AsyncCallback, object, IAsyncResult> work,
            Action<IAsyncResult> complete,
            int timeout = 5000)
        {
            AsyncSynchroniser.MakeSynchronous(work, complete, timeout)();
        }

        public static TResult AsyncPatternWithResult<TResult>(
            Func<AsyncCallback, object, IAsyncResult> work,
            Func<IAsyncResult, TResult> complete,
            int timeout = 5000)
        {
            return AsyncSynchroniser.MakeSynchronous(work, complete, timeout)();
        }

        public static void AsyncPattern<TArg>(
            Func<TArg, AsyncCallback, object, IAsyncResult> work,
            TArg asyncArgument,
            Action<IAsyncResult> complete,
            int timeout = 5000)
        {
            AsyncSynchroniser.MakeSynchronous(work, asyncArgument, complete, timeout)();
        }

        public static TResult AsyncPatternWithResult<TArg, TResult>(
            Func<TArg, AsyncCallback, object, IAsyncResult> work,
            TArg asyncArgument,
            Func<IAsyncResult, TResult> complete,
            int timeout = 5000)
        {
            return AsyncSynchroniser.MakeSynchronous(work, asyncArgument, complete, timeout)();
        }

        public static void AsyncPattern<TArg, TArg2>(
            Func<TArg, TArg2, AsyncCallback, object, IAsyncResult> work,
            TArg asyncArgument,
            TArg2 asyncArgument2,
            Action<IAsyncResult> complete,
            int timeout = 5000)
        {
            AsyncSynchroniser.MakeSynchronous(work, asyncArgument, asyncArgument2, complete, timeout)();
        }

        public static TResult AsyncPatternWithResult<TArg, TArg2, TResult>(
            Func<TArg, TArg2, AsyncCallback, object, IAsyncResult> work,
            TArg asyncArgument,
            TArg2 asyncArgument2,
            Func<IAsyncResult, TResult> complete,
            int timeout = 5000)
        {
            return AsyncSynchroniser.MakeSynchronous(work, asyncArgument, asyncArgument2, complete, timeout)();
        }

        public static void AsyncPattern<TArg, TArg2, TArg3>(
            Func<TArg, TArg2, TArg3, AsyncCallback, object, IAsyncResult> work,
            TArg asyncArgument,
            TArg2 asyncArgument2,
            TArg3 asyncArgument3,
            Action<IAsyncResult> complete,
            int timeout = 5000)
        {
            AsyncSynchroniser.MakeSynchronous(work, asyncArgument, asyncArgument2, asyncArgument3, complete, timeout)();
        }

        public static TResult AsyncPatternWithResult<TArg, TArg2, TArg3, TResult>(
            Func<TArg, TArg2, TArg3, AsyncCallback, object, IAsyncResult> work,
            TArg asyncArgument,
            TArg2 asyncArgument2,
            TArg3 asyncArgument3,
            Func<IAsyncResult, TResult> complete,
            int timeout = 5000)
        {
            return AsyncSynchroniser.MakeSynchronous(work, asyncArgument, asyncArgument2, asyncArgument3, complete, timeout)();
        }
    }
}
